#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <mpi.h>
#include <omp.h>
#include "lib.h"
#include "hash.h"

int main(int argc, char **argv){
  int cntPalavras, rank, porcentagem, tamanhoNomeComputador;
  FILE *fp;
  char nomeComputador[MPI_MAX_PROCESSOR_NAME], *buffer, *buffer2, *token, *listaTokens = " \n\t\r,-";
  hash_set set;
  
  /* verifica se os argumentos de entrada foram passados */
	if(argc != 2){
		printf("Argumentos invalidos!!!\n");
		printf("Use: %s <porcentagem>\n", argv[0]);
		exit(1);
	}

  /* converte o primeiro argumento de entrada em um inteiro */
	porcentagem = strtoul(argv[1], NULL, 0);

  /* verifica se a porcentagem passada e valida */  
  if(porcentagem > 100 || porcentagem <= 0){
    printf("Porcentagem invalida!!!\n");
    printf("Entre com um valor inteiro no intervalo (0, 100]");
    exit(1);
  }
  
  /* inicia a sessao MPI */
  MPI_Init(&argc, &argv);

  /* obtem o rank do processo */
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  /* obtem o nome do computador */
  MPI_Get_processor_name(nomeComputador, &tamanhoNomeComputador);

  if (rank == 0){
    int nProcessos, cntProcessos, tamanhoArquivo, inicioParticao, fimParticao;
    
    /* pega o numero de processos */
    MPI_Comm_size(MPI_COMM_WORLD, &nProcessos);
    
    criar_hash_set(&set, 1000000);

    /* abre o arquivo de texto */
    fp = fopen("palavras.txt", "r");

    /* verifica se o arquivo foi aberto */
    if(fp == NULL){
      printf("ERRO: Nao foi possivel abrir o arquivo palavras.txt!!!\n");
      exit(1);
    }

    /* calcula o tamanho do arquivo */
    fseek(fp, 0, SEEK_END);
    tamanhoArquivo = ftell(fp);

    /* retorna o ponteiro para o inicio do arquivo */
    fseek(fp, 0, SEEK_SET);

    /* aloca o buffer para carregar o arquivo na memoria */
    buffer = (char *)malloc(sizeof(char) * tamanhoArquivo);

    /* carrega o arquivo na memoria */
    fread(buffer, sizeof(char), tamanhoArquivo, fp);

    /* fecha o arquivo */
    fclose(fp);

    cntPalavras = 0; /* inicia o numero de palavras como zero */
    token = strtok(buffer, listaTokens); /* inicia a divisao do texto */

    printf("[%s] -> Gerando hash para remover repeticoes.\n", nomeComputador);

    /* divide o texto em tokens ate que o mesmo termine */
    while(token != NULL){
      /* remove os caracteres nao alfanumericos do token*/
      if(limpa(token)){
        
        minuscula(token); /* passa o token para minuscula */
        
        /* insere no hash set (para remover palavras repetidas) */
        inserir_hash_set(&set, token);
        ++cntPalavras;
      }
      token = strtok(NULL, listaTokens);
    }
    
    /* imprime informacoes do hash */
    printf("[%s] -> Numero de palavras no texto: %d.\n", nomeComputador, cntPalavras);
    printf("[%s] -> Numero de palavras no HashSet: %d.\n", nomeComputador, set.n_palavras);
    printf("[%s] -> Numero de colisoes: %d.\n", nomeComputador, set.n_colisoes);
    printf("[%s] -> Tamanho do Hash: %d.\n", nomeComputador, set.tamanho);
    
    /* desaloca o texto da memoria */
    free(buffer);
    
    /* gera um novo texto sem palavras repetidas e sem caracteres nao alpfanumericos */
    buffer = hash_set_para_string(&set);

    /* desaloca o hash set */
    deletar_hash_set(&set);

    printf("[%s] -> Enviando particoes para os outros nos.\n", nomeComputador);

    /* divide o texto em particoes e envia para os processos */
    inicioParticao = fimParticao = 0;
    for(cntProcessos = 0; cntProcessos < nProcessos - 1; ++cntProcessos){
      /* calcula o fim da particao */
      fimParticao = ((float)strlen(buffer) / nProcessos) * (cntProcessos + 1);

      /* ajusta o fim da particao para nao dividir uma palavra */
      while(buffer[fimParticao] != ' '){
        ++fimParticao;
      }

      /* envia a particao para o processo de destino */
      MPI_Send(&buffer[inicioParticao], 
        fimParticao - inicioParticao, 
        MPI_CHAR, 
        cntProcessos + 1, 
        1, 
        MPI_COMM_WORLD);

      /* calcula o inicio da proxima particao */
      inicioParticao = fimParticao;
    }

    printf("[%s] -> Processando sua particao.\n", nomeComputador);

    /* mantem um ponteiro para o inicio do buffer (para poder deleta-lo) */
    buffer2 = buffer;

    /* cria um ponteiro para a parte do buffer que deve ser processada pelo frontend */
    buffer += inicioParticao;
    
    /* processa a particao do no */
    rodar(buffer, porcentagem, nomeComputador);

    /* libera o buffer */
    free(buffer2);    
  }else{
    MPI_Status status;
    int nProcessos;

    /* tenta alocar o buffer e verifica se foi possivel */
    buffer = (char *)malloc(sizeof(char) * 200000000);
    if(buffer == NULL){
      printf("ERRO: Nao foi possivel criar o buffer!!!\n");

      exit(1);
    }

    printf("[%s] -> Recebendo particao.\n", nomeComputador);

    /* recebe o particao do texto e a salva no buffer */
    MPI_Recv(buffer, 200000000, MPI_CHAR, 0, 1, MPI_COMM_WORLD, &status);

    printf("[%s] -> Processando particao.\n", nomeComputador);
    
    /* processa o buffer */
    rodar(buffer, porcentagem, nomeComputador);

    /* desaloca o buffer */
    free(buffer);

    /* imprime algumas informacoes do processo e o numero de palindromos encontrados na particao */
    MPI_Comm_size(MPI_COMM_WORLD, &nProcessos);
    fprintf(stderr, 
      "[%s] -> Terminou processo %d de %d.\n", 
      nomeComputador, 
      rank + 1, 
      nProcessos);
  }
  
  MPI_Finalize();
  return 0;
}
